﻿using HttpExtensions;
using log4net.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace OKDebug.webservice
{
    public class WebserviceHelper
    {
        /// <summary>
        /// 调用Web服务
        /// Calls a web service.
        /// </summary>
        /// <param name="url">服务URL / Service URL</param>
        /// <param name="apiName">API名称 / API name</param>
        /// <param name="expireSecond">过期时间（秒）/ Expiration time in seconds</param>
        /// <param name="parameters">调用参数 / Invocation parameters</param>
        /// <returns>调用结果 / Invocation result</returns>
        public OpenToolResult<string> CallWebservice(string url, string apiName, long expireSecond = 86400, params object[] parameters)
        {
            OpenToolResult<string> result = new OpenToolResult<string>();

            try
            {
                CheckExpireTime(url, apiName, expireSecond);
                var wsInfo = OpenWebserviceInfo.OpenWebservice.FirstOrDefault(x => x.WebserviceUrl == url && x.OperationName == apiName);
                if (wsInfo == null)
                {
                    result.IsSuccess = false;
                    result.Message = "本地无法加载远程webservice服务。Cannot load the remote webservice locally.OpenWebserviceDoc=" + OpenWebserviceInfo.OpenWebservice.Count;
                    return result;
                }

                if (parameters == null && wsInfo.ParameterNames.Count > 0 || parameters != null && parameters.Length != wsInfo.ParameterNames.Count)
                {
                    result.IsSuccess = false;
                    result.Message = $"远程服务接口参数个数和你传入的参数个数不匹配。远程服务参数个数:{wsInfo.ParameterNames.Count}, 本地传入参数个数： {parameters?.Length ?? 0}。Parameter count mismatch: remote service has {wsInfo.ParameterNames.Count}, provided {parameters?.Length ?? 0}.";
                    return result;
                }

                Dictionary<string, string> dicParams = new Dictionary<string, string>();
                if (parameters != null)
                {
                    for (int i = 0; i < wsInfo.ParameterNames.Count; i++)
                    {
                        dicParams.Add(wsInfo.ParameterNames[i], XmlConvertor.SerializeObjectToXml(parameters[i]));
                    }
                }

                var response = InvokeService(url, apiName, dicParams, wsInfo.Namespace);
                result.Result = response;
                result.IsSuccess = true;
                result.Message = "success";
            }
            catch (Exception ex)
            {
                result.Result = ex.ToString() + ex.StackTrace;
                result.IsSuccess = false;
                result.Message = "fail";
            }

            return result;
        }

        /// <summary>
        /// 检查Web服务缓存是否过期，并在必要时更新
        /// Checks if the web service cache is expired and updates it if necessary.
        /// </summary>
        /// <param name="url">服务的URL / The URL of the service</param>
        /// <param name="apiName">API名称 / API name</param>
        /// <param name="expireSecond">过期时间（秒）/ Expiration time in seconds</param>
        private void CheckExpireTime(string url, string apiName, long expireSecond = 86400)
        {
            try
            {
                string namespaceName = "http://tempuri.org/";
                List<string> parameterNames = GetParameterNamesFromWsdlAsync(url, apiName, ref namespaceName);
                var webServiceInfo = OpenWebserviceInfo.OpenWebservice.FirstOrDefault(x => x.WebserviceUrl == url && x.OperationName == apiName);
                if (webServiceInfo == null)
                {
                    // 创建新的缓存条目
                    webServiceInfo = new OpenWebserviceDocCache
                    {
                        WebserviceUrl = url,
                        OperationName = apiName,
                        ParameterNames = parameterNames,
                        Namespace = namespaceName,
                        ExpireSeconds = expireSecond,
                        ResetTime = DateTime.Now
                    };
                    OpenWebserviceInfo.OpenWebservice.Add(webServiceInfo);
                }
                else
                {
                    if (webServiceInfo.ResetTime.AddSeconds(expireSecond) < DateTime.Now)
                    {
                        //重置时间并重新加载Web服务的文档
                        webServiceInfo.ResetTime = DateTime.Now;
                        webServiceInfo.ParameterNames = parameterNames;
                        webServiceInfo.Namespace = namespaceName;
                        webServiceInfo.ExpireSeconds = expireSecond;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"加载wsdl服务文档[{url}]失败。Failed to load WSDL document: {ex.Message}{ex.StackTrace}");
            }
        }

        /// <summary>
        /// 从WSDL文档获取参数名列表
        /// Retrieves a list of parameter names from the WSDL document.
        /// </summary>
        /// <param name="wsdlUrl">WSDL文档的URL / URL of the WSDL document</param>
        /// <param name="operationName">操作名称 / Name of the operation</param>
        /// <returns>参数名列表 / List of parameter names</returns>
        private List<string> GetParameterNamesFromWsdlAsync(string wsdlUrl, string operationName,ref string namespaceName)
        {
            var parameterNames = new List<string>();
            XmlDocument wsdlDoc = new XmlDocument();
            string wsdlContent;
            try
            {
                wsdlContent = GetHttpString(wsdlUrl);
            }
            catch (Exception ex)
            {
                throw new Exception("网络访问出错 " + ex.ToString() + ex.StackTrace);
            }

            //wsdlDoc.Load(wsdlUrl);  // 加载WSDL文档 / Load the WSDL document
            wsdlDoc.LoadXml(wsdlContent);
            //_siLogger.Info(wsdlContent);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(wsdlDoc.NameTable);
            nsmgr.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/");
            nsmgr.AddNamespace("s", "http://www.w3.org/2001/XMLSchema");

            // 构建XPath查询字符串 / Construct XPath query string
            string xpath = $"//wsdl:portType/wsdl:operation[@name='{operationName}']/wsdl:input";
            XmlNode inputNode = wsdlDoc.SelectSingleNode(xpath, nsmgr);
            if (inputNode != null)
            {
                string messageName = inputNode.Attributes["message"].Value.Split(':')[1]; // 解析消息名称
                XmlNode messageNode = wsdlDoc.SelectSingleNode($"//wsdl:message[@name='{messageName}']/wsdl:part", nsmgr);
                if (messageNode != null)
                {
                    string elementName = messageNode.Attributes["element"].Value.Split(':')[1]; // 解析元素名称
                    XmlNode elementNode = wsdlDoc.SelectSingleNode($"//s:schema/s:element[@name='{elementName}']", nsmgr);
                    if (elementNode != null)
                    {
                        XmlNode sequenceNode = elementNode.SelectSingleNode("s:complexType/s:sequence", nsmgr);
                        if (sequenceNode != null)
                        {
                            foreach (XmlNode param in sequenceNode.ChildNodes)
                            {
                                parameterNames.Add(param.Attributes["name"].Value);
                            }
                        }
                    }
                }
            }

            nsmgr = new XmlNamespaceManager(wsdlDoc.NameTable);
            nsmgr.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/");
            nsmgr.AddNamespace("soap", "http://schemas.xmlsoap.org/wsdl/soap/");

            // 构建XPath查询字符串 / Construct XPath query string
            xpath = $"//wsdl:binding/wsdl:operation[@name='{operationName}']/soap:operation";
            XmlNode operationNode = wsdlDoc.SelectSingleNode(xpath, nsmgr);
            if (operationNode != null && operationNode.Attributes["soapAction"] != null)
            {
                string soapAction = operationNode.Attributes["soapAction"].Value;
                namespaceName = soapAction.Substring(0, soapAction.LastIndexOf('/') + 1);
            }

            return parameterNames;
        }

        /// <summary>
        /// 调用Web服务
        /// Invokes a web service.
        /// </summary>
        /// <param name="url">服务URL / Service URL</param>
        /// <param name="methodName">方法名称 / Method name</param>
        /// <param name="parameters">参数字典 / Dictionary of parameters</param>
        /// <param name="actionHeader">命名空间，默认为http://tempuri.org/ / Action header(Namespace), defaults to http://tempuri.org/</param>
        /// <returns>服务调用结果 / Service invocation result</returns>
        private string InvokeService(string url, string methodName, Dictionary<string, string> parameters, string actionHeader = "http://tempuri.org/")
        {
            var content = BuildSoapEnvelope(methodName, parameters, actionHeader);
            var response = PostHttpString(url, content, $"{actionHeader}{methodName}");
            return response;
        }

        /// <summary>
        /// 构建SOAP消息信封
        /// Builds a SOAP message envelope.
        /// </summary>
        /// <param name="methodName">方法名称 / Method name</param>
        /// <param name="parameters">参数字典 / Dictionary of parameters</param>
        /// <param name="actionHeader">动作头部 / Action header</param>
        /// <returns>SOAP信封字符串 / SOAP envelope string</returns>
        private string BuildSoapEnvelope(string methodName, Dictionary<string, string> parameters, string actionHeader)
        {
            var sb = new StringBuilder();
            sb.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>");
            sb.Append(@"<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">");
            sb.Append(@"<soap:Body>");
            sb.Append($"<{methodName} xmlns=\"{actionHeader}\">");

            // 遍历字典并添加XML元素
            foreach (var param in parameters)
            {
                sb.Append($"<{param.Key}>{param.Value}</{param.Key}>");
            }

            sb.Append($"</{methodName}>");
            sb.Append(@"</soap:Body>");
            sb.Append(@"</soap:Envelope>");

            return sb.ToString();
        }

        /// <summary>
        /// 获取响应json
        /// </summary>
        /// <param name="xml"></param>
        /// <param name="ns"></param>
        /// <returns></returns>
        public string GetResponseConent(string xml, string ns)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
            nsMgr.AddNamespace("soapenv", ns);
            nsMgr.AddNamespace("ns", ns);
            XmlNode node = xmlDoc.SelectSingleNode("//ns:return", nsMgr);
            if (null == node)
            {
                return string.Empty;
            }

            return node.InnerText;
        }

        /// <summary>
        /// 获取远程http数据
        /// </summary>
        /// <param name="url">url</param>
        /// <returns></returns>
        public string GetHttpString(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                string responseText = reader.ReadToEnd();
                return responseText;
            }
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postData"></param>
        /// <returns></returns>
        public string PostHttpString(string url,string postData,string soapAction)
        {
            byte[] byteArray = Encoding.UTF8.GetBytes(postData);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            //request.ContentType = "application/x-www-form-urlencoded";
            request.ContentType = "text/xml";
            request.ContentLength = byteArray.Length;
            //request.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/xml");
            //request.Headers.ContentType.CharSet = "utf-8";
            request.Headers.Add("SOAPAction", soapAction);

            using (Stream dataStream = request.GetRequestStream())
            {
                dataStream.Write(byteArray, 0, byteArray.Length);
            }

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string responseFromServer = reader.ReadToEnd();
                    return responseFromServer;
                }
            }
        }
    }
}
